// Package bouncer provides a client implementation of the HTTP
// bouncer protocol described at http://bouncer.googlecode.com/.
//
// In a nutshell, it allows two clients to establish a connection to
// each other by both connecting to a shared URL served by a bouncer
// server. When both clients connect to that URL, the bouncer server
// proxies the session between them.
//
// Typically, there are no restrictions on the URL used to bounce,
// provided that it it served by the bouncer server. A pair of clients
// can use a long, random "private" URL for one-on-one communication
// (but should still authenticate each other and set up encryption),
// or a bunch of clients can connect to a "shared" URL to be paired
// off at random (think omegle for software).
//
// WARNING: bouncer servers provide simple connection mediation. They
// do not provide:
//
//   * Peer authentication. You must set that up yourself after the
//     bouncer connects you.
//
//   * Private communication. Even if both clients connect to the
//     bouncer over https, the bouncer itself has access to the
//     cleartext of your session. If this is an issue for you, you
//     should set up a secure transport yourself after the connection
//     establishes, using e.g. crypto/tls.
package bouncer

import (
	"crypto/tls"
	"errors"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
)

// Try to connect to a peer through the bouncer service at the given
// URL. If the bouncer service is operational and another client is
// waiting at the same URL, Dial returns a TCP connection to that
// other client, via the bouncer server.
//
// To help the clients decide who should do what in client-server
// protocols, Dial returns (<connection>, true, nil) for one client, and
// (<connection>, false, nil) for the other. This is just a hint,
// clients are free to use it or ignore it.
func Dial(addr string) (net.Conn, bool, error) {
	dest, err := url.ParseWithReference(addr)
	if err != nil {
		return nil, false, err
	}

	var conn net.Conn
	switch dest.Scheme {
	case "http":
		conn, err = net.Dial("tcp", dest.Host)
	case "https":
		conn, err = tls.Dial("tcp", dest.Host, nil)
	default:
		err = errors.New("Unknown scheme")
	}
	if err != nil {
		return nil, false, err
	}

	client := httputil.NewClientConn(conn, nil)
	req, err := http.NewRequest("GET", dest.Path, nil)
	if err != nil {
		conn.Close()
		return nil, false, err
	}
	req.Header.Add("upgrade", "TCP")

	resp, err := client.Do(req)
	if err != nil {
		conn.Close()
		return nil, false, err
	}
	if resp.StatusCode != 101 {
		conn.Close()
		return nil, false, errors.New(http.StatusText(resp.StatusCode))
	}

	is_creator := resp.Header.Get("x-bouncer-order") == "first"
	conn, buf := client.Hijack()
	return &bouncer{conn, buf}, is_creator, nil
}

type bouncer struct {
	net.Conn
	buf io.Reader
}

func (b *bouncer) Read(buf []byte) (int, error) {
	return b.buf.Read(buf)
}
